/**
 * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "panda_file_translator.h"

#include <string>
#include <string_view>
#include <vector>

#include "plugins/ecmascript/runtime/class_info_extractor.h"
#include "plugins/ecmascript/runtime/class_linker/program_object-inl.h"
#include "plugins/ecmascript/runtime/global_env.h"
#include "plugins/ecmascript/runtime/ic/profile_type_info.h"
#include "plugins/ecmascript/runtime/interpreter/interpreter.h"
#include "plugins/ecmascript/runtime/js_array.h"
#include "plugins/ecmascript/runtime/js_function.h"
#include "plugins/ecmascript/runtime/js_thread.h"
#include "plugins/ecmascript/runtime/literal_data_extractor.h"
#include "plugins/ecmascript/runtime/object_factory.h"
#include "plugins/ecmascript/runtime/tagged_array.h"
#include "plugins/ecmascript/runtime/ic/ic_runtime_stub.h"
#include "libpandabase/mem/mem.h"
#include "libpandabase/utils/logger.h"
#include "libpandabase/utils/utf.h"
#include "libpandafile/bytecode_instruction-inl.h"
#include "libpandafile/class_data_accessor-inl.h"

#include "libpandabase/os/filesystem.h"

namespace ark::ecmascript {
PandaFileTranslator::PandaFileTranslator(EcmaVM *vm)
    : ecmaVm_(vm), factory_(vm->GetFactory()), thread_(vm->GetJSThread())
{
}

JSHandle<Program> PandaFileTranslator::TranslatePandaFile(EcmaVM *vm, const panda_file::File &pf,
                                                          const PandaString &methodName)
{
    PandaFileTranslator translator(vm);
    translator.TranslateClasses(pf, methodName);
    auto result = translator.GenerateProgram(pf);
    return JSHandle<Program>(translator.thread_, result);
}

void PandaFileTranslator::QuickPandaFile(EcmaVM *vm, const panda_file::File &pf)
{
    PandaFileTranslator translator(vm);
    translator.TranslateClasses(pf, "");
}

template <class T, class... Args>
static T *InitializeMemory(T *mem, Args... args)
{
    return new (mem) T(std::forward<Args>(args)...);
}

const JSMethod *PandaFileTranslator::FindMethods(uint32_t offset) const
{
    auto pred = [offset](const JSMethod &method) { return method.GetFileId().GetOffset() == offset; };
    auto it = std::find_if(methods_->begin(), methods_->end(), pred);
    if (it != methods_->end()) {
        return &*it;
    }
    return nullptr;
}

void PandaFileTranslator::TranslateClasses(const panda_file::File &pf, const PandaString &methodName)
{
    Span<const uint32_t> classIndexes = pf.GetClasses();
    methods_ = MakePandaUnique<PandaList<JSMethod>>();

    panda_file::File::StringData sd = {static_cast<uint32_t>(methodName.size()),
                                       reinterpret_cast<const uint8_t *>(methodName.c_str())};

    auto aotManager = Runtime::GetCurrent()->GetClassLinker()->GetAotManager();
    auto filename = os::GetAbsolutePath(pf.GetFilename());
    auto aotPfile = aotManager->FindPandaFile(filename);

    for (const uint32_t index : classIndexes) {
        panda_file::File::EntityId classId(index);
        if (pf.IsExternal(classId)) {
            continue;
        }
        panda_file::ClassDataAccessor cda(pf, classId);
        auto descriptor = cda.GetDescriptor();

        compiler::AotClass aotClass =
            (aotPfile != nullptr) ? aotPfile->GetClass(classId.GetOffset()) : compiler::AotClass::Invalid();

        cda.EnumerateMethods([this, &aotClass, &sd, &pf, &descriptor](panda_file::MethodDataAccessor &mda) {
            TranslateMethod(aotClass, sd, pf, descriptor, mda);
        });
    }
}

void PandaFileTranslator::TranslateMethod(const compiler::AotClass &aotClass, const panda_file::File::StringData &sd,
                                          const panda_file::File &pf, const uint8_t *descriptor,
                                          panda_file::MethodDataAccessor &mda)
{
    auto codeId = mda.GetCodeId();
    ASSERT(codeId.has_value());

    panda_file::CodeDataAccessor codeDataAccessor(pf, codeId.value());
    uint32_t codeSize = codeDataAccessor.GetCodeSize();

    if (mainMethodIndex_ == 0 && pf.GetStringData(mda.GetNameId()) == sd) {
        mainMethodIndex_ = mda.GetMethodId().GetOffset();
    }

    panda_file::ProtoDataAccessor pda(pf, mda.GetProtoId());

    auto *classLinker = Runtime::GetCurrent()->GetClassLinker();
    auto *extension = classLinker->GetExtension(ark::panda_file::SourceLang::ECMASCRIPT);
    auto klass = extension->GetClass(descriptor);
    if (klass == nullptr) {
        // Case for ark_aot. This is workaround.
        klass = classLinker->LoadClass(&pf, descriptor, ark::panda_file::SourceLang::ECMASCRIPT);
    }

    ASSERT(klass != nullptr);  // Very important assert.

    methods_->emplace_back(klass, &pf, mda.GetMethodId(), codeDataAccessor.GetCodeId(), mda.GetAccessFlags(),
                           codeDataAccessor.GetNumArgs(), reinterpret_cast<const uint16_t *>(pda.GetShorty().Data()));
    JSMethod *method = &methods_->back();

    if (aotClass.IsValid()) {
        auto entry = aotClass.FindMethodCodeEntry(methods_->size() - 1);
        if (entry != nullptr) {
            method->SetCompiledEntryPoint(entry);
        } else {
            method->SetInterpreterEntryPoint();
        }
    } else {
        method->SetInterpreterEntryPoint();
    }

    method->SetCallTypeFromAnnotation();
    method->InitProfileVector();
    const uint8_t *insns = codeDataAccessor.GetInstructions();

    if (translatedCode_.find(insns) == translatedCode_.end()) {
        translatedCode_.insert(insns);
        if (pf.GetHeader()->quickenedFlag != 0U) {
            TranslateBytecode<true>(codeSize, insns, pf, method);
        } else {
            TranslateBytecode<false>(codeSize, insns, pf, method);
        }
    }
}

Program *PandaFileTranslator::GenerateProgram(const panda_file::File &pf)
{
    EcmaHandleScope handleScope(thread_);

    JSHandle<Program> program = factory_->NewProgram();
    JSHandle<EcmaString> location =
        factory_->NewFromStdStringUnCheck(pf.GetFilename(), true, ark::SpaceType::SPACE_TYPE_NON_MOVABLE_OBJECT);

    // +1 for program
    JSHandle<ConstantPool> constpool = factory_->NewConstantPool(constpoolIndex_ + 1);
    program->SetConstantPool(thread_, constpool.GetTaggedValue());
    program->SetLocation(thread_, location.GetTaggedValue());

    JSHandle<GlobalEnv> env = ecmaVm_->GetGlobalEnv();
    JSHandle<JSHClass> dynclass = JSHandle<JSHClass>::Cast(env->GetFunctionClassWithProto());
    JSHandle<JSHClass> normalDynclass = JSHandle<JSHClass>::Cast(env->GetFunctionClassWithoutProto());
    JSHandle<JSHClass> asyncDynclass = JSHandle<JSHClass>::Cast(env->GetAsyncFunctionClass());
    JSHandle<JSHClass> generatorDynclass = JSHandle<JSHClass>::Cast(env->GetGeneratorFunctionClass());
    JSHandle<JSHClass> asyncgeneratorDynclass = JSHandle<JSHClass>::Cast(env->GetAsyncGeneratorFunctionClass());

    for (const auto &it : constpoolMap_) {
        ConstPoolValue value(it.second);
        if (value.GetConstpoolType() == ConstPoolType::STRING) {
            panda_file::File::EntityId id(it.first);
            auto foundStr = pf.GetStringData(id);
            auto string = factory_->GetRawStringFromStringTable(foundStr.data, foundStr.utf16Length, foundStr.isAscii,
                                                                ark::SpaceType::SPACE_TYPE_NON_MOVABLE_OBJECT);
            if (string == nullptr) {
                LOG(FATAL, ECMASCRIPT) << "Not enough memory";
            }
            constpool->Set(thread_, value.GetConstpoolIndex(), JSTaggedValue(string));
        } else if (value.GetConstpoolType() == ConstPoolType::BASE_FUNCTION) {
            ASSERT(mainMethodIndex_ != it.first);
            panda_file::File::EntityId id(it.first);
            auto method = const_cast<JSMethod *>(FindMethods(it.first));
            ASSERT(method != nullptr);

            JSHandle<JSFunction> jsFunc = factory_->NewJSFunctionByDynClass(
                method, dynclass, FunctionKind::BASE_CONSTRUCTOR, ark::SpaceType::SPACE_TYPE_NON_MOVABLE_OBJECT);
            constpool->Set(thread_, value.GetConstpoolIndex(), jsFunc.GetTaggedValue());
            jsFunc->SetConstantPool(thread_, constpool.GetTaggedValue());
        } else if (value.GetConstpoolType() == ConstPoolType::NC_FUNCTION) {
            ASSERT(mainMethodIndex_ != it.first);
            panda_file::File::EntityId id(it.first);
            auto method = const_cast<JSMethod *>(FindMethods(it.first));
            ASSERT(method != nullptr);

            JSHandle<JSFunction> jsFunc = factory_->NewJSFunctionByDynClass(
                method, normalDynclass, FunctionKind::NORMAL_FUNCTION, ark::SpaceType::SPACE_TYPE_NON_MOVABLE_OBJECT);
            constpool->Set(thread_, value.GetConstpoolIndex(), jsFunc.GetTaggedValue());
            jsFunc->SetConstantPool(thread_, constpool.GetTaggedValue());
        } else if (value.GetConstpoolType() == ConstPoolType::GENERATOR_FUNCTION) {
            ASSERT(mainMethodIndex_ != it.first);
            panda_file::File::EntityId id(it.first);
            auto method = const_cast<JSMethod *>(FindMethods(it.first));
            ASSERT(method != nullptr);

            JSHandle<JSFunction> jsFunc =
                factory_->NewJSFunctionByDynClass(method, generatorDynclass, FunctionKind::GENERATOR_FUNCTION,
                                                  ark::SpaceType::SPACE_TYPE_NON_MOVABLE_OBJECT);
            // 26.3.4.3 prototype
            // Whenever a GeneratorFunction instance is created another ordinary object is also created and
            // is the initial value of the generator function's "prototype" property.
            JSHandle<JSTaggedValue> objFun = env->GetObjectFunction();
            JSHandle<JSObject> initialGeneratorFuncPrototype = factory_->NewJSObjectByConstructor(
                JSHandle<JSFunction>(objFun), objFun, ark::SpaceType::SPACE_TYPE_NON_MOVABLE_OBJECT);
            JSObject::SetPrototype(thread_, initialGeneratorFuncPrototype, env->GetGeneratorPrototype());
            jsFunc->SetProtoOrDynClass(thread_, initialGeneratorFuncPrototype);

            constpool->Set(thread_, value.GetConstpoolIndex(), jsFunc.GetTaggedValue());
            jsFunc->SetConstantPool(thread_, constpool.GetTaggedValue());
        } else if (value.GetConstpoolType() == ConstPoolType::ASYNC_FUNCTION) {
            ASSERT(mainMethodIndex_ != it.first);
            panda_file::File::EntityId id(it.first);
            auto method = const_cast<JSMethod *>(FindMethods(it.first));
            ASSERT(method != nullptr);

            JSHandle<JSFunction> jsFunc = factory_->NewJSFunctionByDynClass(
                method, asyncDynclass, FunctionKind::ASYNC_FUNCTION, ark::SpaceType::SPACE_TYPE_NON_MOVABLE_OBJECT);
            constpool->Set(thread_, value.GetConstpoolIndex(), jsFunc.GetTaggedValue());
            jsFunc->SetConstantPool(thread_, constpool.GetTaggedValue());
        } else if (value.GetConstpoolType() == ConstPoolType::ASYNC_GENERATOR_FUNCTION) {
            ASSERT(mainMethodIndex_ != it.first);
            panda_file::File::EntityId id(it.first);
            auto method = const_cast<JSMethod *>(FindMethods(it.first));
            ASSERT(method != nullptr);

            JSHandle<JSFunction> jsFunc = factory_->NewJSFunctionByDynClass(
                method, asyncgeneratorDynclass, FunctionKind::ASYNC_GENERATOR_FUNCTION,
                ark::SpaceType::SPACE_TYPE_NON_MOVABLE_OBJECT);
            constpool->Set(thread_, value.GetConstpoolIndex(), jsFunc.GetTaggedValue());
            jsFunc->SetConstantPool(thread_, constpool.GetTaggedValue());
        } else if (value.GetConstpoolType() == ConstPoolType::CLASS_FUNCTION) {
            ASSERT(mainMethodIndex_ != it.first);
            panda_file::File::EntityId id(it.first);
            auto method = const_cast<JSMethod *>(FindMethods(it.first));
            ASSERT(method != nullptr);
            JSHandle<ClassInfoExtractor> classInfoExtractor = factory_->NewClassInfoExtractor(method);
            constpool->Set(thread_, value.GetConstpoolIndex(), classInfoExtractor.GetTaggedValue());
        } else if (value.GetConstpoolType() == ConstPoolType::METHOD) {
            ASSERT(mainMethodIndex_ != it.first);
            panda_file::File::EntityId id(it.first);
            auto method = const_cast<JSMethod *>(FindMethods(it.first));
            ASSERT(method != nullptr);

            JSHandle<JSFunction> jsFunc = factory_->NewJSFunctionByDynClass(
                method, normalDynclass, FunctionKind::NORMAL_FUNCTION, ark::SpaceType::SPACE_TYPE_NON_MOVABLE_OBJECT);
            constpool->Set(thread_, value.GetConstpoolIndex(), jsFunc.GetTaggedValue());
            jsFunc->SetConstantPool(thread_, constpool.GetTaggedValue());
        } else if (value.GetConstpoolType() == ConstPoolType::OBJECT_LITERAL) {
            size_t index = it.first;
            JSMutableHandle<TaggedArray> elements(thread_, JSTaggedValue::Undefined());
            JSMutableHandle<TaggedArray> properties(thread_, JSTaggedValue::Undefined());
            LiteralDataExtractor::ExtractObjectDatas(thread_, &pf, index, elements, properties, this);
            JSHandle<JSObject> obj = JSObject::CreateObjectFromProperties(
                thread_, properties, ark::SpaceType::SPACE_TYPE_NON_MOVABLE_OBJECT);

            JSMutableHandle<JSTaggedValue> key(thread_, JSTaggedValue::Undefined());
            JSMutableHandle<JSTaggedValue> valueHandle(thread_, JSTaggedValue::Undefined());
            size_t elementsLen = elements->GetLength();
            for (size_t i = 0; i < elementsLen; i += 2) {  // 2: Each literal buffer contains a pair of key-value.
                key.Update(elements->Get(i));
                if (key->IsHole()) {
                    break;
                }
                valueHandle.Update(elements->Get(i + 1));
                JSObject::DefinePropertyByLiteral(thread_, obj, key, valueHandle);
            }
            constpool->Set(thread_, value.GetConstpoolIndex(), obj.GetTaggedValue());
        } else if (value.GetConstpoolType() == ConstPoolType::ARRAY_LITERAL) {
            size_t index = it.first;
            JSHandle<TaggedArray> literal =
                LiteralDataExtractor::GetDatasIgnoreType(thread_, &pf, static_cast<size_t>(index));
            uint32_t length = literal->GetLength();

            JSHandle<JSArray> arr(
                JSArray::ArrayCreate(thread_, JSTaggedNumber(length), ark::SpaceType::SPACE_TYPE_NON_MOVABLE_OBJECT));
            arr->SetElements(thread_, literal);
            constpool->Set(thread_, value.GetConstpoolIndex(), arr.GetTaggedValue());
        } else if (value.GetConstpoolType() == ConstPoolType::TAGGED_ARRAY) {
            size_t index = it.first;
            JSHandle<TaggedArray> literal =
                LiteralDataExtractor::GetDatasIgnoreType(thread_, &pf, static_cast<size_t>(index), this);
            constpool->Set(thread_, value.GetConstpoolIndex(), literal.GetTaggedValue());
        }
    }
    {
        auto method = const_cast<JSMethod *>(FindMethods(mainMethodIndex_));
        ASSERT(method != nullptr);
        JSHandle<JSFunction> mainFunc = factory_->NewJSFunctionByDynClass(
            method, dynclass, FunctionKind::BASE_CONSTRUCTOR, ark::SpaceType::SPACE_TYPE_NON_MOVABLE_OBJECT);
        mainFunc->SetConstantPool(thread_, constpool.GetTaggedValue());
        program->SetMainFunction(thread_, mainFunc.GetTaggedValue());
        program->SetMethodsData(methods_.release());
        // link program
        constpool->Set(thread_, constpoolIndex_, program.GetTaggedValue());
    }

    DefineClassInConstPool(constpool);
    return *program;
}

template <bool IS_QUICKENED = false>
void PandaFileTranslator::FixInstructionId32(const BytecodeInstruction &inst, [[maybe_unused]] uint32_t index,
                                             uint32_t fixOrder) const
{
    using EnumT = std::conditional_t<IS_QUICKENED, BytecodeInstruction::ECMASCRIPT_Opcode, BytecodeInstruction::Opcode>;
    using R = BytecodeInstructionResolver<IS_QUICKENED>;
    constexpr int PAYLOAD_OFFSET = IS_QUICKENED ? 1 : 2;

    // NOLINTNEXTLINE(hicpp-use-auto)
    auto pc = const_cast<uint8_t *>(inst.GetAddress());
    auto format = inst.GetFormat<EnumT>();
    switch (format) {
        case BytecodeInstruction::Format::ID32: {
            uint8_t size = sizeof(uint32_t);
            // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
            if (memcpy_s(pc + FixInstructionIndex::FIX_ONE, size, &index, size) != EOK) {
                LOG_ECMA(FATAL) << "memcpy_s failed";
                UNREACHABLE();
            }
            break;
        }
        case BytecodeInstruction::Format::PREF_ID32: {
            uint8_t size = sizeof(uint32_t);
            // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
            if (memcpy_s(pc + FixInstructionIndex::FIX_TWO, size, &index, size) != EOK) {
                LOG_ECMA(FATAL) << "memcpy_s failed";
                UNREACHABLE();
            }
            break;
        }
        case R::template Get<BytecodeInstruction::Format::PREF_ID16_V8>(): {
            uint8_t size = sizeof(uint16_t);
            // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
            if (memcpy_s(pc + PAYLOAD_OFFSET, size, &index, size) != EOK) {
                LOG_ECMA(FATAL) << "memcpy_s failed";
                UNREACHABLE();
            }
            break;
        }
        // Create RegexpWithLiteral
        case R::template Get<BytecodeInstruction::Format::PREF_ID32_IMM8>():
        /* The following 3 formats matches for Ld/StLexDyn where the string_id should be fixed and the remaining
         * immediates are untouched */
        case R::template Get<BytecodeInstruction::Format::PREF_ID32_IMM4_IMM4>():
        case R::template Get<BytecodeInstruction::Format::PREF_ID32_IMM8_IMM8>():
        case R::template Get<BytecodeInstruction::Format::PREF_ID32_IMM16_IMM16>():
        case R::template Get<BytecodeInstruction::Format::PREF_ID32_V8>():
        case R::template Get<BytecodeInstruction::Format::PREF_ID32_V8_V8>(): {
            uint8_t size = sizeof(uint32_t);
            // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
            if (memcpy_s(pc + PAYLOAD_OFFSET, size, &index, size) != EOK) {
                LOG_ECMA(FATAL) << "memcpy_s failed";
                UNREACHABLE();
            }
            break;
        }
        case R::template Get<BytecodeInstruction::Format::PREF_ID16>():
        case R::template Get<BytecodeInstruction::Format::PREF_IMM16>(): {
            ASSERT(static_cast<uint16_t>(index) == index);
            uint16_t u16Index = index;
            uint8_t size = sizeof(uint16_t);
            // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
            if (memcpy_s(pc + PAYLOAD_OFFSET, size, &u16Index, size) != EOK) {
                LOG_ECMA(FATAL) << "memcpy_s failed";
                UNREACHABLE();
            }
            break;
        }
        case R::template Get<BytecodeInstruction::Format::PREF_ID16_IMM16_V8_V8>(): {
            // Usually, we fix one part of instruction one time. But as for instruction DefineClassWithBuffer,
            // which use both method id and literal buffer id.Using fix_order indicates fix Location.
            if (fixOrder == 0) {
                uint8_t size = sizeof(uint16_t);
                ASSERT(static_cast<uint16_t>(index) == index);
                // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
                if (memcpy_s(pc + PAYLOAD_OFFSET, size, &index, size) != EOK) {
                    LOG_ECMA(FATAL) << "memcpy_s failed";
                    UNREACHABLE();
                }
                break;
            }
            if (fixOrder == 1) {
                ASSERT(static_cast<uint16_t>(index) == index);
                uint16_t u16Index = index;
                uint8_t size = sizeof(uint16_t);
                // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
                if (memcpy_s(pc + PAYLOAD_OFFSET + 2, size, &u16Index, size) != EOK) {
                    LOG_ECMA(FATAL) << "memcpy_s failed";
                    UNREACHABLE();
                }
                break;
            }
            break;
        }
        default:
            UNREACHABLE();
    }
}

template <bool IS_QUICKENED>
void PandaFileTranslator::TranslateBytecode(uint32_t insSz, const uint8_t *insArr, const panda_file::File &pf,
                                            const JSMethod *method)
{
    using EnumT = std::conditional_t<IS_QUICKENED, BytecodeInstruction::ECMASCRIPT_Opcode, BytecodeInstruction::Opcode>;
    using R = BytecodeInstructionResolver<IS_QUICKENED>;

    auto bcIns = BytecodeInstruction(insArr);
    auto bcInsLast = bcIns.JumpTo(insSz);

    while (bcIns.GetAddress() != bcInsLast.GetAddress()) {
        if (bcIns.HasFlag<EnumT>(BytecodeInstruction::Flags::STRING_ID) &&
            BytecodeInstruction::HasId(bcIns.GetFormat<EnumT>(), 0)) {
            auto index = GetOrInsertConstantPool(ConstPoolType::STRING, bcIns.GetId<EnumT>().AsFileId().GetOffset());
            FixInstructionId32<IS_QUICKENED>(bcIns, index);
        } else {
            auto opcode = bcIns.GetOpcode<EnumT>();
            switch (opcode) {
                uint32_t index;
                uint32_t methodId;
                case R::template Get<BytecodeInstruction::Opcode::ECMA_DEFINEFUNCDYN_PREF_ID16_V8>():
                    methodId = pf.ResolveMethodIndex(method->GetFileId(), bcIns.GetId<EnumT>().AsIndex()).GetOffset();
                    index = GetOrInsertConstantPool(ConstPoolType::BASE_FUNCTION, methodId);
                    FixInstructionId32<IS_QUICKENED>(bcIns, index);
                    break;
                case R::template Get<BytecodeInstruction::Opcode::ECMA_DEFINENCFUNCDYN_PREF_ID16_V8>():
                    methodId = pf.ResolveMethodIndex(method->GetFileId(), bcIns.GetId<EnumT>().AsIndex()).GetOffset();
                    index = GetOrInsertConstantPool(ConstPoolType::NC_FUNCTION, methodId);
                    FixInstructionId32<IS_QUICKENED>(bcIns, index);
                    break;
                case R::template Get<BytecodeInstruction::Opcode::ECMA_DEFINEGENERATORFUNC_PREF_ID16_V8>():
                    methodId = pf.ResolveMethodIndex(method->GetFileId(), bcIns.GetId<EnumT>().AsIndex()).GetOffset();
                    index = GetOrInsertConstantPool(ConstPoolType::GENERATOR_FUNCTION, methodId);
                    FixInstructionId32<IS_QUICKENED>(bcIns, index);
                    break;
                case R::template Get<BytecodeInstruction::Opcode::ECMA_DEFINEASYNCFUNC_PREF_ID16_V8>():
                    methodId = pf.ResolveMethodIndex(method->GetFileId(), bcIns.GetId<EnumT>().AsIndex()).GetOffset();
                    index = GetOrInsertConstantPool(ConstPoolType::ASYNC_FUNCTION, methodId);
                    FixInstructionId32<IS_QUICKENED>(bcIns, index);
                    break;
                case R::template Get<BytecodeInstruction::Opcode::ECMA_DEFINEASYNCGENERATORFUNC_PREF_ID16_V8>():
                    methodId = pf.ResolveMethodIndex(method->GetFileId(), bcIns.GetId<EnumT>().AsIndex()).GetOffset();
                    index = GetOrInsertConstantPool(ConstPoolType::ASYNC_GENERATOR_FUNCTION, methodId);
                    FixInstructionId32<IS_QUICKENED>(bcIns, index);
                    break;
                case R::template Get<BytecodeInstruction::Opcode::ECMA_DEFINEMETHOD_PREF_ID16_V8>():
                    methodId = pf.ResolveMethodIndex(method->GetFileId(), bcIns.GetId<EnumT>().AsIndex()).GetOffset();
                    index = GetOrInsertConstantPool(ConstPoolType::METHOD, methodId);
                    FixInstructionId32<IS_QUICKENED>(bcIns, index);
                    break;
                case R::template Get<BytecodeInstruction::Opcode::ECMA_CREATEOBJECTWITHBUFFER_PREF_ID16>():
                case R::template Get<BytecodeInstruction::Opcode::ECMA_CREATEOBJECTHAVINGMETHOD_PREF_ID16>():
                    index = GetOrInsertConstantPool(ConstPoolType::OBJECT_LITERAL,
                                                    bcIns.GetId<EnumT>().AsFileId().GetOffset());
                    FixInstructionId32<IS_QUICKENED>(bcIns, index);
                    break;
                case R::template Get<BytecodeInstruction::Opcode::ECMA_CREATEARRAYWITHBUFFER_PREF_ID16>():
                    index = GetOrInsertConstantPool(ConstPoolType::ARRAY_LITERAL,
                                                    bcIns.GetId<EnumT>().AsFileId().GetOffset());
                    FixInstructionId32<IS_QUICKENED>(bcIns, index);
                    break;
                case R::template Get<BytecodeInstruction::Opcode::ECMA_LDEVALBINDINGS_PREF_ID16>():
                case R::template Get<BytecodeInstruction::Opcode::ECMA_DEFINECLASSPRIVATEFIELDS_PREF_ID16_V8>(): {
                    index = GetOrInsertConstantPool(ConstPoolType::TAGGED_ARRAY,
                                                    bcIns.GetId<EnumT>().AsFileId().GetOffset());
                    FixInstructionId32<IS_QUICKENED>(bcIns, index);
                    break;
                }
                case R::template Get<BytecodeInstruction::Opcode::ECMA_DEFINECLASSWITHBUFFER_PREF_ID16_IMM16_V8_V8>():
                    methodId = pf.ResolveMethodIndex(method->GetFileId(), bcIns.GetId<EnumT>().AsIndex()).GetOffset();
                    index = GetOrInsertConstantPool(ConstPoolType::CLASS_FUNCTION, methodId);
                    FixInstructionId32<IS_QUICKENED>(bcIns, index);
                    index = GetOrInsertConstantPool(
                        ConstPoolType::TAGGED_ARRAY,
                        bcIns.GetImm<R::template Get<BytecodeInstruction::Format::PREF_ID16_IMM16_V8_V8>()>());
                    FixInstructionId32<IS_QUICKENED>(bcIns, index, 1);
                    break;
                default:
                    break;
            }
        }
        UpdateICOffset<IS_QUICKENED>(const_cast<JSMethod *>(method), insSz, bcIns);
        bcIns = bcIns.GetNext<EnumT>();
    }
}

template <bool IS_QUICKENED>
void PandaFileTranslator::UpdateICOffset(JSMethod *method, uint32_t insSz, const BytecodeInstruction &inst) const
{
    using EnumT = std::conditional_t<IS_QUICKENED, BytecodeInstruction::ECMASCRIPT_Opcode, BytecodeInstruction::Opcode>;
    using R = BytecodeInstructionResolver<IS_QUICKENED>;

    auto opcode = inst.GetOpcode<EnumT>();
    uint32_t bcOffset = inst.GetAddress() - method->GetInstructions();
    uint32_t slotSize = 0;
    using ICMappingType = JSMethod::ICMappingType;
    static_assert(std::is_pointer<ICMappingType>::value);
    using ICMappingElementType = std::remove_pointer<ICMappingType>::type;
    ICMappingType icMapping = method->GetICMapping();

    if (method->GetSlotSize() > std::numeric_limits<ICMappingElementType>::max()) {
        if (icMapping != nullptr) {
            mem::InternalAllocator<>::GetInternalAllocatorFromRuntime()->Free(icMapping);
            method->SetICMapping(nullptr);
        }
        return;
    }
    switch (opcode) {
        case R::template Get<BytecodeInstruction::Opcode::ECMA_TRYLDGLOBALBYNAME_PREF_ID32>():
        case R::template Get<BytecodeInstruction::Opcode::ECMA_TRYSTGLOBALBYNAME_PREF_ID32>():
        case R::template Get<BytecodeInstruction::Opcode::ECMA_LDGLOBALVAR_PREF_ID32>():
        case R::template Get<BytecodeInstruction::Opcode::ECMA_STGLOBALVAR_PREF_ID32>():
            slotSize = ICRuntimeStub::SlotSizeForGlobalICByName();
            break;
        case R::template Get<BytecodeInstruction::Opcode::ECMA_LDOBJBYVALUE_PREF_V8>():
        case R::template Get<BytecodeInstruction::Opcode::ECMA_STOBJBYVALUE_PREF_V8_V8>():
        case R::template Get<BytecodeInstruction::Opcode::ECMA_STOWNBYVALUE_PREF_V8_V8>():
            slotSize = ICRuntimeStub::SlotSizeForICByValue();
            break;
        case R::template Get<BytecodeInstruction::Opcode::ECMA_LDOBJBYNAME_PREF_ID32>():
        case R::template Get<BytecodeInstruction::Opcode::ECMA_STOBJBYNAME_PREF_ID32_V8>():
        case R::template Get<BytecodeInstruction::Opcode::ECMA_STOWNBYNAME_PREF_ID32_V8>():
            slotSize = ICRuntimeStub::SlotSizeForICByName();
            break;
        default:
            return;
    }

    if (icMapping == nullptr && insSz < ProfileTypeInfo::MAX_FUNCTION_SIZE) {
        icMapping =
            mem::InternalAllocator<>::GetInternalAllocatorFromRuntime()->AllocArray<ICMappingElementType>(insSz);
        memset_s(icMapping, insSz, 0, insSz);
        method->SetICMapping(icMapping);
    }
    if (icMapping != nullptr) {
        // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
        icMapping[bcOffset] = method->GetSlotSize();
        method->AddSlotSize(slotSize);
    }
}

uint32_t PandaFileTranslator::GetOrInsertConstantPool(ConstPoolType type, uint32_t offset)
{
    auto it = constpoolMap_.find(offset);
    if (it != constpoolMap_.cend()) {
        ConstPoolValue value(it->second);
        return value.GetConstpoolIndex();
    }
    ASSERT(constpoolIndex_ != UINT32_MAX);
    uint32_t index = constpoolIndex_++;
    ConstPoolValue value(type, index);
    constpoolMap_.insert({offset, value.GetValue()});
    return index;
}

JSHandle<JSFunction> PandaFileTranslator::DefineMethodInLiteral(uint32_t methodId, FunctionKind kind) const
{
    auto method = const_cast<JSMethod *>(FindMethods(methodId));
    ASSERT(method != nullptr);

    JSHandle<GlobalEnv> env = thread_->GetEcmaVM()->GetGlobalEnv();
    JSHandle<JSHClass> functionClass;
    if (kind == FunctionKind::NORMAL_FUNCTION) {
        functionClass = JSHandle<JSHClass>::Cast(env->GetFunctionClassWithoutProto());
    } else {
        functionClass = JSHandle<JSHClass>::Cast(env->GetGeneratorFunctionClass());
    }
    JSHandle<JSFunction> jsFunc =
        factory_->NewJSFunctionByDynClass(method, functionClass, kind, ark::SpaceType::SPACE_TYPE_NON_MOVABLE_OBJECT);

    if (kind == FunctionKind::GENERATOR_FUNCTION) {
        JSHandle<JSTaggedValue> objFun = env->GetObjectFunction();
        JSHandle<JSObject> initialGeneratorFuncPrototype = factory_->NewJSObjectByConstructor(
            JSHandle<JSFunction>(objFun), objFun, ark::SpaceType::SPACE_TYPE_NON_MOVABLE_OBJECT);
        JSObject::SetPrototype(thread_, initialGeneratorFuncPrototype, env->GetGeneratorPrototype());
        jsFunc->SetProtoOrDynClass(thread_, initialGeneratorFuncPrototype);
    } else if (kind == FunctionKind::ASYNC_GENERATOR_FUNCTION) {
        JSHandle<JSTaggedValue> objFun = env->GetObjectFunction();
        JSHandle<JSObject> initialAsyncGeneratorFuncPrototype = factory_->NewJSObjectByConstructor(
            JSHandle<JSFunction>(objFun), objFun, ark::SpaceType::SPACE_TYPE_NON_MOVABLE_OBJECT);
        JSObject::SetPrototype(thread_, initialAsyncGeneratorFuncPrototype, env->GetAsyncGeneratorPrototype());
        jsFunc->SetProtoOrDynClass(thread_, initialAsyncGeneratorFuncPrototype);
    }
    jsFunc->SetupFunctionLength(thread_);
    return jsFunc;
}

void PandaFileTranslator::DefineClassInConstPool(const JSHandle<ConstantPool> &constpool) const
{
    uint32_t length = constpool->GetLength();
    uint32_t index = 0;
    while (index < length - 1) {
        JSTaggedValue value = constpool->Get(index);
        if (!value.IsClassInfoExtractor()) {
            index++;
            continue;
        }

        // Here, using a law: when inserting ctor in index of constantpool, the index + 1 location will be inserted by
        // corresponding class literal. Because translator fixes ECMA_DEFINECLASSWITHBUFFER two consecutive times.
        JSTaggedValue nextValue = constpool->Get(index + 1);
        ASSERT(nextValue.IsTaggedArray());

        JSHandle<ClassInfoExtractor> extractor(thread_, value);
        JSHandle<TaggedArray> literal(thread_, nextValue);
        ClassInfoExtractor::BuildClassInfoExtractorFromLiteral(thread_, extractor, literal);
        JSHandle<JSFunction> cls = ClassHelper::DefineClassTemplate(thread_, extractor, constpool);
        constpool->Set(thread_, index, cls);
        index += 2;  // 2: pair of extractor and literal
    }
}
}  // namespace ark::ecmascript
